/*
 ---------------------------------------------------------------------------
 Copyright (c) 1998-2006, Brian Gladman, Worcester, UK. All rights reserved.

 LICENSE TERMS

 The free distribution and use of this software in both source and binary
 form is allowed (with or without changes) provided that:

   1. distributions of this source code include the above copyright
      notice, this list of conditions and the following disclaimer;

   2. distributions in binary form include the above copyright
      notice, this list of conditions and the following disclaimer
      in the documentation and/or other associated materials;

   3. the copyright holder's name is not used to endorse products
      built using this software without specific written permission.

 DISCLAIMER

 This software is provided 'as is' with no explicit or implied warranties
 in respect of its properties, including, but not limited to, correctness
 and/or fitness for purpose.
 ---------------------------------------------------------------------------
 Issue Date: 20/12/2007

 My thanks to Colin Sinclair for finding an error and suggesting a number
 of improvements to this code
*/

#include <stdio.h>
#include "../../common/types.h"
#include "inc/ccm.h"
#include "inc/mode_hdr.h"
#include "inc/debug.h"


#if defined(__cplusplus)
extern "C"
{
#endif

#define BLOCK_SIZE      AES_BLOCK_SIZE      /* block length                */
#define BLK_ADR_MASK    (BLOCK_SIZE - 1)    /* mask for 'in block' address */
#define INIT_STATE      10
#define AUTH_STATE      11
#define MSG_STATE       12

#define ctr_len(x)      (((*(u8*)(x)) & 0x07) + 1)

#define clr_ctr(x,l)    memset((u8*)(x) + BLOCK_SIZE - (l), 0, (l))

#define set_ctr(x,v)                                \
  {                                                 \
    u8 *_p = (u8*)(x) + BLOCK_SIZE;       \
    length_t _t = (v), _l = ctr_len(x);             \
    do                                              \
    {                                               \
      *--_p = (u8)_t; _t >>= 8;                \
    } while (--_l);                                 \
  }

#define inc_ctr(x)                                  \
  {                                                 \
    u8 *_p = (u8*)(x) + BLOCK_SIZE;       \
    length_t _l = ctr_len(x);                       \
    while (_l-- && ++(*--_p) == 0) ;                \
  }

#ifdef LONG_MESSAGES

#  define mlen_len(x)                     \
    (((x) & 0xff00000000000000ll) ? 7 :     \
     ((x) & 0xffff000000000000ll) ? 6 :     \
     ((x) & 0xffffff0000000000ll) ? 5 :     \
     ((x) & 0xffffffff00000000ll) ? 4 :     \
     ((x) & 0xffffffffff000000ll) ? 3 :     \
     ((x) & 0xffffffffffff0000ll) ? 2 : 1)

#else
#  define mlen_len(x) (((x) & 0xff000000) ? 3 : ((x) & 0xffff0000) ? 2 : 1)
#endif
int j;

u32 ccm_init_and_key(                 /* initialise mode and set key */
            const unsigned char key[],     /* the key value               */
            unsigned long key_len,         /* and its length in bytes     */
            ccm_ctx ctx[1])                /* the mode context            */
{
  ctx->state = RETURN_GOOD;
  if ((key_len != 16) && (key_len != 24) && (key_len != 32))
  {
    ctx->state = CCM_BAD_KEY;
  }
  aes_encrypt_key(key, (int)key_len, ctx->aes);
  return RETURN_GOOD;
}


u32 ccm_init_message(                 /* initialise for a new message */
            const unsigned char iv[],      /* the initialisation vector    */
            unsigned long iv_len,          /* the nonce length             */
            length_t hdr_len,              /* the associated data length   */
            length_t msg_len,              /* message data length          */
            unsigned long tag_len,         /* authentication field length  */
            ccm_ctx ctx[1])                /* the mode context             */
{
  u32 cnt;


  ctx->state = RETURN_GOOD;
  if ((tag_len < 2) || (tag_len > 16) || (tag_len & 1))
  {
    ctx->state = CCM_BAD_AUTH_FIELD_LENGTH;
  }
  else if (iv_len && (iv_len < 7) || (iv_len > 13))
  {
    ctx->state = CCM_BAD_NONCE_LENGTH;
  }
  if (ctx->state)
  {
    return ctx->state;
  }

  ctx->iv_len = iv_len;
  ctx->hdr_len = hdr_len;
  ctx->msg_len = msg_len;
  ctx->tag_len = tag_len;
  ctx->cnt = 0;
  ctx->txt_acnt = 0;
  ctx->hdr_lim = hdr_len;

  UI8_PTR(ctx->ctr_val)[0] = (u8)(iv_len ?            \
                             BLOCK_SIZE - 2 - iv_len :     \
                             mlen_len(ctx->msg_len));

  /* move the iv into the block */
  for(cnt = 1;
      cnt < (u32)BLOCK_SIZE - UI8_PTR(ctx->ctr_val)[0] - 1;
      ++cnt)
  {
    UI8_PTR(ctx->ctr_val)[cnt] = iv[cnt - 1];
  }

  /* clear the counter value */
  clr_ctr(ctx->ctr_val, UI8_PTR(ctx->ctr_val)[0] + 1);
  /* copy block to CBC buffer */
  memcpy(ctx->cbc_buf, ctx->ctr_val, BLOCK_SIZE);
  /* set initial counter     */
  UI8_PTR(ctx->ctr_val)[BLOCK_SIZE - 1] = 1;
  /* store the message length */
  set_ctr(ctx->cbc_buf, ctx->msg_len);

  UI8_PTR(ctx->cbc_buf)[0] |= (ctx->hdr_lim ? 0x40 : 0) +
                              ((ctx->tag_len - 2) << 2);

  //printf("B_0:\n");
  //for (j=0;j<16;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //}
  //printf("\n");
  /* encrypt the cbc block    */
  aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);

  //printf("AES(B_0):\n");
  //for (j=0;j<16;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //}
  //printf("\n");

  if (ctx->hdr_len)
  {
    /* encode the length field if there is some associated data */
    if (ctx->hdr_len < 65536 - 256)
    {
      UI8_PTR(ctx->cbc_buf)[0] ^= (u8)(ctx->hdr_lim >> 8);
      UI8_PTR(ctx->cbc_buf)[1] ^= (u8) ctx->hdr_lim;
      ctx->cnt = 2;
    }
#ifndef LONG_MESSAGES
    else
    {
      ctx->state = CCM_AUTH_LENGTH_ERROR;
      return ctx->state;
    }
#else
    else if (ctx->hdr_len < 0x0000000100000000ll)
    {
      UI8_PTR(ctx->cbc_buf)[0] ^= 0xff;
      UI8_PTR(ctx->cbc_buf)[1] ^= 0xfe;
      UI8_PTR(ctx->cbc_buf)[2] ^= (u8)(ctx->hdr_lim >> 24);
      UI8_PTR(ctx->cbc_buf)[3] ^= (u8)(ctx->hdr_lim >> 16);
      UI8_PTR(ctx->cbc_buf)[4] ^= (u8)(ctx->hdr_lim >>  8);
      UI8_PTR(ctx->cbc_buf)[5] ^= (u8) ctx->hdr_lim;
      ctx->cnt = 6;
    }
    else
    {
      UI8_PTR(ctx->cbc_buf)[0] ^= 0xff;
      UI8_PTR(ctx->cbc_buf)[1] ^= 0xff;
      UI8_PTR(ctx->cbc_buf)[2] ^= (u8)(ctx->hdr_lim >> 56);
      UI8_PTR(ctx->cbc_buf)[3] ^= (u8)(ctx->hdr_lim >> 48);
      UI8_PTR(ctx->cbc_buf)[4] ^= (u8)(ctx->hdr_lim >> 40);
      UI8_PTR(ctx->cbc_buf)[5] ^= (u8)(ctx->hdr_lim >> 32);
      UI8_PTR(ctx->cbc_buf)[6] ^= (u8)(ctx->hdr_lim >> 24);
      UI8_PTR(ctx->cbc_buf)[7] ^= (u8)(ctx->hdr_lim >> 16);
      UI8_PTR(ctx->cbc_buf)[8] ^= (u8)(ctx->hdr_lim >>  8);
      UI8_PTR(ctx->cbc_buf)[9] ^= (u8) ctx->hdr_lim;
      ctx->cnt = 10;
    }
#endif
    ctx->hdr_lim += ctx->cnt;
    ctx->state = AUTH_STATE;
  }
  else    /* there is no associated data  */
  {
    ctx->cnt = 0;
    ctx->state = MSG_STATE;
  }
  //printf("AES(B_0) XOR L(a):\n");
  //for (j=0;j<16;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //}
  //printf("\n");
  ctx->hdr_lim = ctx->hdr_len + ctx->cnt;
  ctx->txt_acnt = ctx->cnt;

  return RETURN_GOOD;
}


u32 ccm_auth_header(                  /* authenticate the header */
            const unsigned char hdr[],     /* the header buffer       */
            length_t hdr_len,         /* and its length in bytes */
            ccm_ctx ctx[1])                /* the mode context        */
{
  u32  cnt = 0;
  u32  b_pos = ((u32)ctx->cnt) & BLK_ADR_MASK;


  if (!hdr_len)
  {
    return RETURN_GOOD;
  }

  if (ctx->state < 0)
  {
    return ctx->state;
  }

  if (ctx->state != AUTH_STATE)
  {
    return (ctx->state = CCM_BAD_AUTH_CALL);
  }

  if (ctx->cnt + hdr_len > ctx->hdr_lim)
  {
    return (ctx->state = CCM_AUTH_LENGTH_ERROR);
  }

  if (!((hdr - (UI8_PTR(ctx->cbc_buf) + b_pos)) & BUF_ADRMASK))
  {
    while ((cnt < hdr_len) && (b_pos & BUF_ADRMASK))
    {
      UI8_PTR(ctx->cbc_buf)[b_pos++] ^= hdr[cnt++];
    }
    while (cnt + BUF_INC <= hdr_len && b_pos <= BLOCK_SIZE - BUF_INC)
    {
      *UNIT_PTR(UI8_PTR(ctx->cbc_buf) + b_pos) ^= *UNIT_PTR(hdr + cnt);
      cnt += BUF_INC; b_pos += BUF_INC;
    }
    //printf("a:AES(B_0) XOR B_1:\n");
    //for (j=0;j<16;j++) {
    // printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
    //}
    //printf("\n");
    while (cnt + BLOCK_SIZE <= hdr_len)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
      //printf("a: X_%d:\n",cnt/16+1);
      //for (j=0;j<16;j++) {
      //  printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
      //}
      //printf("\n");
      xor_block_aligned(ctx->cbc_buf, ctx->cbc_buf, hdr + cnt);
      cnt += BLOCK_SIZE;
    }
  } /* if */
  else
  {
    while (cnt < hdr_len && b_pos < BLOCK_SIZE)
    {
      UI8_PTR(ctx->cbc_buf)[b_pos++] ^= hdr[cnt++];
    }
    //printf("b: AES(B_0) XOR B_1:\n");
    //for (j=0;j<16;j++) {
    // printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
    //}
    //printf("\n");

    while (cnt + BLOCK_SIZE <= hdr_len)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
      //printf("b: X_%d:\n",cnt/16+1);
      //for (j=0;j<16;j++) {
      //  printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
      //  }
      //printf("\n");
      xor_block(ctx->cbc_buf, ctx->cbc_buf, hdr + cnt);
      cnt += BLOCK_SIZE;
    }
  } /* else */

  while (cnt < hdr_len)
  {
    if (b_pos == BLOCK_SIZE)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
      //printf("X_%d:\n",cnt/16+1);
      //for (j=0;j<16;j++) {
      //  printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
      //  }
      //printf("\n");
      b_pos = 0;
    }
    UI8_PTR(ctx->cbc_buf)[b_pos++] ^= hdr[cnt++];

  }

  ctx->cnt += cnt;
  if ((b_pos == BLOCK_SIZE) || (ctx->cnt == ctx->hdr_lim))
  {
    aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
  }

  if (ctx->cnt == ctx->hdr_lim)
  {
    ctx->state = MSG_STATE;
    ctx->cnt = 0;
    ctx->txt_acnt = 0;
  }
  return RETURN_GOOD;
}

u32 ccm_auth_data(                    /* authenticate the plaintext */
            const unsigned char data[],    /* the data buffer            */
            unsigned long data_len,        /* and its length in bytes    */
            ccm_ctx ctx[1])                /* the mode context           */
{
  u32  cnt = 0;
  u32  b_pos = ((u32)ctx->txt_acnt) & BLK_ADR_MASK;


  if (ctx->state < 0)
  {
    return ctx->state;
  }

  if (ctx->state == AUTH_STATE)
  {
    return (ctx->state = CCM_AUTH_LENGTH_ERROR);
  }

  if (ctx->txt_acnt + data_len > ctx->msg_len)
  {
    return (ctx->state = CCM_MSG_LENGTH_ERROR);
  }

  if (!((data - (UI8_PTR(ctx->cbc_buf) + b_pos)) & BUF_ADRMASK))
  {
    while (cnt < data_len && (b_pos & BUF_ADRMASK))
    {
      UI8_PTR(ctx->cbc_buf)[b_pos++] ^= data[cnt++];
    }

    while (cnt + BUF_INC <= data_len && b_pos <= BLOCK_SIZE - BUF_INC)
    {
      *UNIT_PTR(UI8_PTR(ctx->cbc_buf) + b_pos) ^= *UNIT_PTR(data + cnt);
      cnt += BUF_INC; b_pos += BUF_INC;
    }

    while (cnt + BLOCK_SIZE <= data_len)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
      xor_block_aligned(ctx->cbc_buf, ctx->cbc_buf, data + cnt);
      cnt += BLOCK_SIZE;
    }
  } /* if */

  else
  {
    while (cnt < data_len && b_pos < BLOCK_SIZE)
    {
      UI8_PTR(ctx->cbc_buf)[b_pos++] ^= data[cnt++];
    }

    while (cnt + BLOCK_SIZE <= data_len)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
      xor_block(ctx->cbc_buf, ctx->cbc_buf, data + cnt);
      cnt += BLOCK_SIZE;
    }
  } /* else */

  while (cnt < data_len)
  {
    if (b_pos == BLOCK_SIZE)
    {
      aes_encrypt(UI8_PTR(ctx->cbc_buf),       \
                  UI8_PTR(ctx->cbc_buf),       \
                  ctx->aes), b_pos = 0;
    }
    UI8_PTR(ctx->cbc_buf)[b_pos++] ^= data[cnt++];
  }

  if (b_pos == BLOCK_SIZE)
  {
    aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
  }

  //printf("AES(B_n-1) XOR B_n:\n");
  //  for (j=0;j<16;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //  }
  //  printf("\n");

  ctx->txt_acnt += cnt;
  return RETURN_GOOD;
}


u32 ccm_crypt_data(                   /* encrypt or decrypt data */
            unsigned char data[],          /* the data buffer         */
            unsigned long data_len,        /* and its length in bytes */
            ccm_ctx ctx[1])                /* the mode context        */
{
  u32  cnt = 0;
  u32  b_pos = ((u32)ctx->cnt) & BLK_ADR_MASK;


  if (ctx->state < 0)
  {
    //dbgp_ccm("   ctx->state\n");
    return ctx->state;
  }

  if (ctx->state == AUTH_STATE)
  {
    //dbgp_ccm("   ctx->AUTHstate\n");
    return (ctx->state = CCM_AUTH_LENGTH_ERROR);
  }

  if (ctx->cnt + data_len > ctx->msg_len)
  {
    //dbgp_ccm("   ctx->msg_len\n");
    return (ctx->state = CCM_MSG_LENGTH_ERROR);
  }

  if (!data_len)
  {
    //dbgp_ccm("   ctx->data_len = 0\n");
    return RETURN_GOOD;
  }

  if (b_pos == 0)
  {
    aes_encrypt(UI8_PTR(ctx->ctr_val), UI8_PTR(ctx->enc_ctr), ctx->aes);
    inc_ctr(ctx->ctr_val);
  }

  if (!((data - (UI8_PTR(ctx->enc_ctr) + b_pos)) & BUF_ADRMASK))
  {
    while ((cnt < data_len) && (b_pos & BUF_ADRMASK))
    {
      data[cnt++] ^= UI8_PTR(ctx->enc_ctr)[b_pos++];
    }

    while ((cnt + BUF_INC <= data_len) && (b_pos <= BLOCK_SIZE - BUF_INC))
    {
      *UNIT_PTR(data + cnt) ^= *UNIT_PTR(UI8_PTR(ctx->enc_ctr) + b_pos);
      cnt += BUF_INC; b_pos += BUF_INC;
    }

    while (cnt + BLOCK_SIZE <= data_len)
    {
      aes_encrypt(UI8_PTR(ctx->ctr_val), UI8_PTR(ctx->enc_ctr), ctx->aes);
      inc_ctr(ctx->ctr_val);
      xor_block_aligned(data + cnt, data + cnt, ctx->enc_ctr);
      cnt += BLOCK_SIZE;
    }
  }
  else
  {
    while ((cnt < data_len) && (b_pos < BLOCK_SIZE))
    {
      data[cnt++] ^= UI8_PTR(ctx->enc_ctr)[b_pos++];
    }

    while (cnt + BLOCK_SIZE <= data_len)
    {
      aes_encrypt(UI8_PTR(ctx->ctr_val), UI8_PTR(ctx->enc_ctr), ctx->aes);
      inc_ctr(ctx->ctr_val);
      xor_block(data + cnt, data + cnt, ctx->enc_ctr);
      cnt += BLOCK_SIZE;
    }
  }

  while (cnt < data_len)
  {
    if (b_pos == BLOCK_SIZE)
    {
      aes_encrypt(UI8_PTR(ctx->ctr_val), UI8_PTR(ctx->enc_ctr), ctx->aes);
      inc_ctr(ctx->ctr_val);
      b_pos = 0;
    }
    data[cnt++] ^= UI8_PTR(ctx->enc_ctr)[b_pos++];
  }

  ctx->cnt += cnt;
  return RETURN_GOOD;
}

u32 ccm_compute_tag(                  /* compute authentication tag */
            unsigned char tag[],           /* the buffer for the tag     */
            unsigned long tag_len,         /* and its length in bytes    */
            ccm_ctx ctx[1])                /* the mode context           */
{
  u32 cnt, pos;


  if (ctx->state < 0)
  {
    return ctx->state;
  }

  if ((tag_len != ctx->tag_len)  ||                    \
      ((ctx->state == INIT_STATE) && ctx->hdr_lim) ||  \
      (ctx->state == AUTH_STATE))
  {
    return (ctx->state = CCM_AUTH_LENGTH_ERROR);
  }

  if (ctx->txt_acnt != ctx->cnt && ctx->cnt > 0)
  {
    return RETURN_ERROR;
  }

  if ((ctx->cnt < ctx->msg_len) ||
      (ctx->cnt + tag_len > ctx->msg_len + ctx->tag_len))
  {
    return (ctx->state = CCM_MSG_LENGTH_ERROR);
  }

  /* if at the start of the authentication field */
  if (tag_len > 0 && ctx->cnt == ctx->msg_len)
  {
    if (ctx->cnt & BLK_ADR_MASK)
    {
        aes_encrypt(UI8_PTR(ctx->cbc_buf), UI8_PTR(ctx->cbc_buf), ctx->aes);
    }
    set_ctr(ctx, 0);
    aes_encrypt(UI8_PTR(ctx->ctr_val), UI8_PTR(ctx->enc_ctr), ctx->aes);
  }


  //printf("After AES:\n");
  //for (j=0;j<16;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //}
  //printf("\n");

  //printf("CBC-MAC:\n");
  //for (j=0;j<tag_len;j++) {
  //   printf("%x ", UI8_PTR(ctx->cbc_buf)[j]);
  //}
  //printf("\n");

  cnt = 0;
  pos = (u32)(ctx->cnt - ctx->msg_len);
  while (cnt < tag_len)
  {
    tag[cnt++] = UI8_PTR(ctx->cbc_buf)[pos] ^ UI8_PTR(ctx->enc_ctr)[pos];
    ++pos;
  }

  return (ctx->cnt == ctx->txt_acnt ? RETURN_GOOD : RETURN_WARN);
}


u32 ccm_end(                          /* clean up and end operation */
            ccm_ctx ctx[1])                /* the mode context           */
{
  memset(ctx, 0, sizeof(ccm_ctx));
  return RETURN_GOOD;
}


u32 ccm_encrypt(                      /* authenticate & encrypt data */
            unsigned char data[],          /* the data buffer             */
            unsigned long data_len,        /* and its length in bytes     */
            ccm_ctx ctx[1])                /* the mode context            */
{
  ccm_auth_data(data, data_len, ctx);
  ccm_crypt_data(data, data_len, ctx);
  return RETURN_GOOD;
}


u32 ccm_decrypt(                      /* decrypt & authenticate data */
            unsigned char data[],          /* the data buffer             */
            unsigned long data_len,        /* and its length in bytes     */
            ccm_ctx ctx[1])                /* the mode context            */
{
  ccm_crypt_data(data, data_len, ctx);
  ccm_auth_data(data, data_len, ctx);
  return RETURN_GOOD;
}


u32 ccm_encrypt_message(              /* encrypt an entire message */
            const unsigned char iv[],      /* the initialisation vector */
            unsigned long iv_len,          /* and its length in bytes   */
            const unsigned char hdr[],     /* the header buffer         */
            length_t hdr_len,              /* and its length in bytes   */
            unsigned char msg[],           /* the message buffer        */
            length_t msg_len,              /* and its length in bytes   */
            unsigned char tag[],           /* the buffer for the tag    */
            unsigned long tag_len,         /* and its length in bytes   */
            ccm_ctx ctx[1])                /* the mode context          */
{

#if 0  /* orig code */

  ccm_init_message(iv, iv_len, hdr_len, msg_len, tag_len, ctx);
  ccm_auth_header(hdr, hdr_len, ctx);
  ccm_encrypt(msg, msg_len, ctx);
  return ccm_compute_tag(tag, tag_len, ctx) ? RETURN_ERROR : RETURN_GOOD;

#else   /* more error checking */

  u32 retVal = ccm_init_message(iv, iv_len, hdr_len,
                                     msg_len, tag_len, ctx);

  if (retVal == RETURN_GOOD)
  {
    retVal = ccm_auth_header(hdr, hdr_len, ctx);
    if (retVal == RETURN_GOOD)
    {
      retVal = ccm_encrypt(msg, msg_len, ctx);
      if (retVal == RETURN_GOOD)
      {
        retVal = ccm_compute_tag(tag, tag_len, ctx);
      }
    }
  }

  return retVal;

#endif
}


u32 ccm_decrypt_message(              /* decrypt an entire message */
            const unsigned char iv[],      /* the initialisation vector */
            unsigned long iv_len,          /* and its length in bytes   */
            const unsigned char hdr[],     /* the header buffer         */
            length_t hdr_len,              /* and its length in bytes   */
            unsigned char msg[],           /* the message buffer        */
            length_t msg_len,              /* and its length in bytes   */
            const unsigned char tag[],     /* the buffer for the tag    */
            unsigned long tag_len,         /* and its length in bytes   */
            unsigned char ctag[],           /* the buffer for the tag    */
            ccm_ctx ctx[1])                /* the mode context          */
{
  u8 local_tag[BLOCK_SIZE];

#if 0  /* orig code */

  u32 rr;

  ccm_init_message(iv, iv_len, hdr_len, msg_len, tag_len, ctx);
  ccm_auth_header(hdr, hdr_len, ctx);
  ccm_decrypt(msg, msg_len, ctx);
  rr = ccm_compute_tag(local_tag, tag_len, ctx);
  return (rr != RETURN_GOOD || memcmp(tag, local_tag, tag_len)) ?  \
          RETURN_ERROR : RETURN_GOOD;

#else   /* more error checking */

  u32 retVal = ccm_init_message(iv, iv_len, hdr_len,
                                     msg_len, tag_len, ctx);
  if (retVal == RETURN_GOOD)
  {
    retVal = ccm_auth_header(hdr, hdr_len, ctx);
    if (retVal == RETURN_GOOD)
    {
      retVal = ccm_decrypt(msg, msg_len, ctx);
      if (retVal == RETURN_GOOD)
      {
        retVal = ccm_compute_tag(local_tag, tag_len, ctx);
        if ((retVal != RETURN_GOOD)
             || memcmp(tag, local_tag, tag_len)
            )
        {
          memcpy (ctag, local_tag, tag_len);
          retVal = RETURN_ERROR;
        }
      }
    }
  }

  return retVal;

#endif
}


#if defined(__cplusplus)
}
#endif
